package com.congee02.dbservlet.framework.orm;

import com.congee02.dbservlet.framework.orm.annotation.Table;
import com.congee02.dbservlet.framework.orm.annotation.TableColumn;
import lombok.SneakyThrows;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * SQL 语句生成器
 * 由对象生成
 */
public final class SqlGenerator<T> {

    /**
     * 映射到数据库表的实体类字节码对象
     */
    private final Class<T> tableEntityClazz;

    /**
     * 数据库表名
     */
    private final String tableName;

    /**
     * 数据库表字段对应的实体类 Getter Setter 方法
     */
    private final transient Set<Map.Entry<String, FieldGetterSetter>> columnGetterSetter;

    public SqlGenerator(Class<T> tableEntityClazz) {
        this.tableEntityClazz = tableEntityClazz;
        Table tableAnnotation = tableEntityClazz.getAnnotation(Table.class);
        if (tableAnnotation == null) {
            throw new RuntimeException(tableEntityClazz + " is not a table entity class.");
        }
        this.tableName = tableAnnotation.table();
        Field[] fields = tableEntityClazz.getDeclaredFields();
        ConcurrentMap<String, FieldGetterSetter> columnGetterSetterMap = new ConcurrentHashMap<>();
        for (Field field : fields) {
            TableColumn tableFieldAnnotation = field.getAnnotation(TableColumn.class);
            if (tableFieldAnnotation != null) {
                columnGetterSetterMap.put(tableFieldAnnotation.columnName(), new FieldGetterSetter(field));
            }
        }
        this.columnGetterSetter = columnGetterSetterMap.entrySet();
    }

    /**
     * 封装字段及其 Getter Setter
     */
    private class FieldGetterSetter {
        private final Field field;
        private final Method getter;
        private final Method setter;

        @SneakyThrows
        public FieldGetterSetter(Field field) {
            this.field = field;
            this.getter = tableEntityClazz.getDeclaredMethod(getterName());
            this.setter = tableEntityClazz.getDeclaredMethod(setterName(), this.field.getType());
        }

        private static final String GETTER_PREFIX = "get";
        private static final String SETTER_PREFIX = "set";
        private String getterName() {
            return GETTER_PREFIX + GetterSetterUtils.getterSetterSuffix(field.getName());
        }
        private String setterName() {
            return SETTER_PREFIX + GetterSetterUtils.getterSetterSuffix(field.getName());
        }

        @SneakyThrows
        private void invokeSetter(Object instance, Object value) {
            setter.invoke(instance, value);
        }

        @SneakyThrows
        private Object invokeGetter(Object instance) {
            return getter.invoke(instance);
        }
    }

    /**
     * 生成批量插入的 SQL
     */
    @SafeVarargs
    public final String insertSql(T... entities) {
        StringBuilder columnBuilder = new StringBuilder("(");
        int n = entities.length;
        StringBuilder[] valueBuilders = new StringBuilder[n];
        for (int i = 0 ; i < n ; i ++ ) {
            valueBuilders[i] = new StringBuilder("(");
        }
        for (Map.Entry<String, FieldGetterSetter> entry : columnGetterSetter) {
            columnBuilder.append(entry.getKey()).append(",");
            for (int i = 0 ; i < n ; i ++ ) {
                Object getterReturn = entry.getValue().invokeGetter(entities[i]);
                if (getterReturn != null) {
                    valueBuilders[i].append("'").append(getterReturn).append("'").append(",");
                } else {
                    valueBuilders[i].append("null").append(",");
                }
            }
        }
        columnBuilder.setCharAt(columnBuilder.length() - 1, ')');
        StringJoiner joiner = new StringJoiner(",");
        for (StringBuilder valueBuilder : valueBuilders) {
            valueBuilder.setCharAt(valueBuilder.length() - 1, ')');
            joiner.add(valueBuilder);
        }
        return "INSERT INTO" + ' ' + tableName + columnBuilder + ' ' + "VALUES" + joiner;
    }

    /**
     * 生成删除的 SQL
     * @param conditions    条件对象
     */
    @SafeVarargs
    public final String deleteSql(T... conditions) {
        String orConditions = orConditions(conditions);
        return "DELETE FROM " + tableName + (orConditions.isBlank() ? "" : " WHERE " + orConditions);
    }

    /**
     * 生成更新的 SQL
     * @param entity        更新值
     * @param conditions    条件
     */
    @SafeVarargs
    public final String updateSql(T entity, T... conditions) {
        String orConditions = orConditions(conditions);
        return "UPDATE " + tableName + " SET " + keyValuePairs(entity, ",") + (orConditions.isBlank() ? "" : " WHERE " + orConditions);
    }

    /**
     * 生成查询的 SQL
     * @param conditions SQL 条件对象
     */
    @SafeVarargs
    public final String selectSqlByPage(int pageIndex, int pageSize, T... conditions) {
        String orConditions = orConditions(conditions);
        return "SELECT * FROM " + tableName + (orConditions.isBlank() ? "" : " WHERE " + orConditions) + " LIMIT " + (pageIndex - 1) * pageSize + "," + pageSize;
    }

    /**
     * 生成查询的 SQL
     * @param conditions SQL 条件对象
     */
    @SafeVarargs
    public final String selectSql(T... conditions) {
        String orConditions = orConditions(conditions);
        return "SELECT * FROM " + tableName + (orConditions.isBlank() ? "" : " WHERE " + orConditions);
    }

    /**
     * 生成形如 key=value 的多个键值对
     * @param condition 条件对象
     * @param delimiter 分隔符
     */
    private String keyValuePairs(T condition, String delimiter) {
        StringJoiner conditionJoiner = new StringJoiner(delimiter);
        for (Map.Entry<String, FieldGetterSetter> entry : columnGetterSetter) {
            FieldGetterSetter fieldGetterSetter = entry.getValue();
            Object getterReturn = fieldGetterSetter.invokeGetter(condition);
            if (getterReturn != null) {
                conditionJoiner.add(keyValuePair(entry.getKey(), getterReturn.toString()));
            }
        }
        return conditionJoiner.toString();
    }

    /**
     * 生成单个 key=value 的键值对
     */
    private String keyValuePair(String key, String value) {
        return key + "=" + '\'' + value + '\'';
    }

    /**
     * 生成单个 key[RelationOperator]value 的关系表达式
     */
    private String keyValuePair(String key, String value, String relationOperator) {
        return key + relationOperator + '\'' + value + '\'';
    }

    @SafeVarargs
    private String orConditions(T... conditions) {
        StringJoiner orJoiner = new StringJoiner(" OR ");
        for (T condition : conditions) {
            orJoiner.add(keyValuePairs(condition, " AND "));
        }
        return orJoiner.toString();
    }

}
